/*Machine is a class that encapsulates methods for generating a timing diagram for a given program,
loading the program, and tracking D-u and register activity */

#include "Machine.h"

namespace TimingDiagram
{

    Machine::Machine(int numDUnits, int numRegisters, int addTime, int mulTime, int iuTime){
        _numDUnits = numDUnits;
        _numRegisters = numRegisters;
        _dUnits = new DUnit[numDUnits];
        _registers = new Register[numRegisters];

        _addTime = addTime;
        _mulTime = mulTime;
        _iuTime = iuTime;

        _loaded = false;

        for(int i=0; i<numDUnits; i++){
            _dUnits[i].second = -1;
        }

        for(int i=0; i<numRegisters; i++){
            _registers[i] = false;
        }
    }

    Machine::~Machine(){
        delete [] _dUnits;
        delete [] _registers;
        _instructions.clear();
    }

    //Method for loading the program from a file
    void Machine::loadProgram(std::string fileName){
        std::ifstream fin;
        fin.open(fileName.c_str(), std::ios::in);

        if(fin.is_open()){
            std::string line;
            while(std::getline(fin, line)){
                std::stringstream ss(line);
                std::string op;
                int dest, src1, src2;

                ss >> op >> dest >> src1 >> src2;

                OPERATOR opSwitch;
                if(op.compare("ADD") == 0)
                    opSwitch = OP_ADD;
                else if(op.compare("MUL") == 0)
                    opSwitch = OP_MUL;

                Instruction inst;
                inst.op = opSwitch;
                inst.dest = dest;
                inst.src1 = src1;
                inst.src2 = src2;
                _instructions.push_back(inst);
            }

            _loaded = true;
        }
        else
            std::cout << "Error opening program file." << std::endl;

        fin.close();

        computeInstructionDependencies();
    }

    //Main method for generating the timing diagram.
    void Machine::outputDiagram(){
        if(_loaded){
            std::stringstream ss;

            if(getNumberOfRegistersUsed() <= _numRegisters){
                for(size_t i=0; i<_instructions.size(); i++){
                    Instruction inst = _instructions[i];

                    _registers[inst.dest] = true;
                    _registers[inst.src1] = true;
                    _registers[inst.src2] = true;

                    int delay;
                    if(i == 0)
                        delay = 0; //*_iuTime ?
                    else
                        delay = _instructions[i-1].startCycle+_iuTime; //minimum delay

                    int lastRunTime = 0; //Tracks the total cycles used by the instruction previously in a D-unit

                    int nextUnit = getNextFreeDUnit();
                    if(nextUnit != -1){ //There is a currently unoccupied D-u
                        if(inst.op == OP_ADD)
                            _dUnits[nextUnit].second = _addTime;
                        else if(inst.op == OP_MUL)
                            _dUnits[nextUnit].second = _mulTime;
                    }
                    else{
                        //Get the D-u that will be available next, and store the cycle count of the instruction currently residing in it
                        nextUnit = getDUnitAvailableNext();
                        lastRunTime = _dUnits[nextUnit].first+_dUnits[nextUnit].second+_iuTime;

                        //Update the D-u with the new instruction's run time
                        if(inst.op == OP_ADD)
                            _dUnits[nextUnit].second = _addTime;
                        else if(inst.op == OP_MUL)
                            _dUnits[nextUnit].second = _mulTime;
                    }

                    //If an instruction is dependent on more than one other instruction, we only need to wait for the longest instruction to finish
                    int longestDependencyDelay = 0;
                    for(size_t j=0; j<inst.dependencies.size(); j++){
                        if(_instructions[inst.dependencies[j]].op == OP_ADD){
                            if((_iuTime+_addTime+_instructions[inst.dependencies[j]].startCycle) > longestDependencyDelay)
                                longestDependencyDelay = _iuTime+_addTime+_instructions[inst.dependencies[j]].startCycle;
                        }
                        else if(_instructions[inst.dependencies[j]].op == OP_MUL){
                            if((_iuTime+_mulTime+_instructions[inst.dependencies[j]].startCycle) > longestDependencyDelay)
                                longestDependencyDelay = _iuTime+_mulTime+_instructions[inst.dependencies[j]].startCycle;
                        }
                    }

                    //If our delay from dependency is longer than the amount of time we would have to wait anyway (in-order issue),
                    //then just set the delay to this dependency directly.
                    if(inst.dependencies.size() > 0)
                        if(longestDependencyDelay > delay)
                            delay = longestDependencyDelay;

                    //If the last instruction to occupy the D-u we wish to use runs longer than our delay from dependency,
                    //set delay to this value.
                    if(lastRunTime > delay)
                        delay = lastRunTime;


                    _dUnits[nextUnit].first = delay; //Keep track of how long this unit was delayed for.
                    _instructions[i].startCycle = delay;
                    std::cout << "Instruction " << i << " Start Cycle: " << _instructions[i].startCycle << std::endl;

                    //With all the delays compensated for, start printing this instructions line of the graph to a stringstream
                    for(int j=0; j<delay; j++){
                        ss << "_|";
                    }
                    for(int j=0; j<_iuTime; j++){
                        ss << "I|";
                    }
                    for(int j=0; j<_dUnits[nextUnit].second; j++){
                        if(inst.op == OP_ADD)
                            ss << "+|";
                        else if(inst.op == OP_MUL)
                            ss << "*|";
                    }
                    ss << std::endl;
                }

                std::cout << std::endl << ss.str(); //Print the graph to standard output
            }
            else
                std::cout << "Not enough registers to execute program." << std::endl;
        }
        else
            std::cout << "Machine::outputDiagram(): program not loaded." << std::endl;
    }

    //Determines the index of the next free D-u
    int Machine::getNextFreeDUnit(){
        for(int i=0; i<_numDUnits; i++){
            if(_dUnits[i].second == -1)
                return i;
        }

        return -1;
    }

    //Determines the index of the D-u that will be next available
    int Machine::getDUnitAvailableNext(){
        int min = _dUnits[0].first+_dUnits[0].second;
        int minIdx = 0;
        for(int i=1; i<_numDUnits; i++){
            if(_dUnits[i].first+_dUnits[i].second < min){
                min = _dUnits[i].first+_dUnits[i].second;
                minIdx = i;
            }
        }

        return minIdx;
    }

    int Machine::getNextAvailableRegister(){
        for(int i=0; i<_numRegisters; i++){
            if(_registers[i] == false)
                return i;
        }

        return -1;
    }

    //Method which iterates over all the instructions, and determines the dependenices between them.
    //For a given instruction i, all instructions (indices) that it depends on will be stored in a vector belonging to i.
    void Machine::computeInstructionDependencies(){
        for(size_t i=0; i<_instructions.size(); i++){
            for(size_t j=i+1; j<_instructions.size(); j++){
                //OI dependency
                if((_instructions[i].dest == _instructions[j].src1) || (_instructions[i].dest == _instructions[j].src2))
                    _instructions[j].dependencies.push_back(i);

                //IO dependency
                if((_instructions[i].src1 == _instructions[j].dest) || (_instructions[i].src2 == _instructions[j].dest))
                    _instructions[j].dependencies.push_back(i);

                //OO dependency
                if(_instructions[i].dest == _instructions[j].dest)
                    _instructions[j].dependencies.push_back(i);
            }
        }
    }

    int Machine::getNumberOfRegistersUsed(){
        std::vector<int> registers;
        std::vector<int>::iterator it;

        for(size_t i=0; i<_instructions.size(); i++){
            int d = _instructions[i].dest;
            int s = _instructions[i].src1;
            int r = _instructions[i].src2;

            it = std::find(registers.begin(), registers.end(), d);
            if(it == registers.end())
                registers.push_back(d);

            it = std::find(registers.begin(), registers.end(), s);
            if(it == registers.end())
                registers.push_back(s);

            it = std::find(registers.begin(), registers.end(), r);
            if(it == registers.end())
                registers.push_back(r);
        }

        return registers.size();
    }

}
